﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using SimpleHID;
using SimpleHID.Report;

namespace SimpleHIDTestApplication.ViewModel
{
  /// <summary>
  /// This class contains properties that the main View can data bind to.
  /// <para>
  /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
  /// </para>
  /// <para>
  /// You can also use Blend to data bind with the tool's support.
  /// </para>
  /// <para>
  /// See http://www.galasoft.ch/mvvm/getstarted
  /// </para>
  /// </summary>
  public class MainViewModel : ViewModelBase
  {
    #region Private

    private BackgroundWorker bw;
    private Model.Model model = new Model.Model();
    private HIDInfoSet selectedHIDUSBDevice;
    private List<string> hidCaps;
    private ButtonBaseItem outputReportItem;

    private bool expandHidCaps;
    private bool expandInputButtonCaps;
    private bool expandInputValueCaps;
    private bool expandOutputButtonCaps;
    private bool expandOutputValueCaps;
    private bool expandFeatureButtonCaps;
    private bool expandFeatureValueCaps;

    #endregion
    
    public bool ExpandHidCaps
    {
      get { return expandHidCaps; }
      set
      {
        if (value == true)
        {
          ExpandInputButtonCaps = false;
          ExpandInputValueCaps = false;
          ExpandOutputButtonCaps = false;
          ExpandOutputValueCaps = false;
          ExpandFeatureButtonCaps = false;
          ExpandFeatureValueCaps = false;
        }
        expandHidCaps = value;
        RaisePropertyChanged("ExpandHidCaps");
      }
    }

    public bool ExpandInputButtonCaps
    {
      get { return expandInputButtonCaps; }
      set
      {
        if (value == true)
        {
          ExpandHidCaps = false;
          ExpandInputValueCaps = false;
          ExpandOutputButtonCaps = false;
          ExpandOutputValueCaps = false;
          ExpandFeatureButtonCaps = false;
          ExpandFeatureValueCaps = false;
        }
        expandInputButtonCaps = value;
        RaisePropertyChanged("ExpandInputButtonCaps");
      }
    }

    public bool ExpandInputValueCaps
    {
      get { return expandInputValueCaps; }
      set
      {
        if (value == true)
        {
          ExpandHidCaps = false;
          ExpandInputButtonCaps = false;
          ExpandOutputButtonCaps = false;
          ExpandOutputValueCaps = false;
          ExpandFeatureButtonCaps = false;
          ExpandFeatureValueCaps = false;
        }
        expandInputValueCaps = value;
        RaisePropertyChanged("ExpandInputValueCaps");
      }
    }

    public bool ExpandOutputButtonCaps
    {
      get { return expandOutputButtonCaps; }
      set
      {
        if (value == true)
        {
          ExpandHidCaps = false;
          ExpandInputButtonCaps = false;
          ExpandInputValueCaps = false;
          ExpandOutputValueCaps = false;
          ExpandFeatureButtonCaps = false;
          ExpandFeatureValueCaps = false;
        }
        expandOutputButtonCaps = value;
        RaisePropertyChanged("ExpandOutputButtonCaps");
      }
    }

    public bool ExpandOutputValueCaps
    {
      get { return expandOutputValueCaps; }
      set
      {
        if (value == true)
        {
          ExpandHidCaps = false;
          ExpandInputButtonCaps = false;
          ExpandInputValueCaps = false;
          ExpandOutputButtonCaps = false;
          ExpandFeatureButtonCaps = false;
          ExpandFeatureValueCaps = false;
        }
        expandOutputValueCaps = value;
        RaisePropertyChanged("ExpandOutputValueCaps");
      }
    }

    public bool ExpandFeatureButtonCaps
    {
      get { return expandFeatureButtonCaps; }
      set
      {
        if (value == true)
        {
          ExpandHidCaps = false;
          ExpandInputButtonCaps = false;
          ExpandInputValueCaps = false;
          ExpandOutputButtonCaps = false;
          ExpandOutputValueCaps = false;
          ExpandFeatureValueCaps = false;
        }
        expandFeatureButtonCaps = value;
        RaisePropertyChanged("ExpandFeatureButtonCaps");
      }
    }

    public bool ExpandFeatureValueCaps
    {
      get { return expandFeatureValueCaps; }
      set
      {
        if (value == true)
        {
          ExpandHidCaps = false;
          ExpandInputButtonCaps = false;
          ExpandInputValueCaps = false;
          ExpandOutputButtonCaps = false;
          ExpandOutputValueCaps = false;
          ExpandFeatureButtonCaps = false;
        }
        expandFeatureValueCaps = value;
        RaisePropertyChanged("ExpandFeatureValueCaps");
      }
    }

    public ObservableCollection<HIDInfoSet> HIDUSBDevices { get; set; }

    public List<string> HidCaps
    {
      get { return hidCaps; }
      set
      {
        hidCaps = value;
        RaisePropertyChanged("HidCaps");
      }
    }

    public ObservableCollection<ButtonBaseItem> ButtonOutputCaps { get; set; }
    public ObservableCollection<ValueBaseItem> ValueOutputCaps { get; set; }
    public ObservableCollection<ButtonBaseItem> ButtonInputCaps { get; set; }
    public ObservableCollection<ValueBaseItem> ValueInputCaps { get; set; }
    public ObservableCollection<ButtonBaseItem> ButtonFeatureCaps { get; set; }
    public ObservableCollection<ValueBaseItem> ValueFeatureCaps { get; set; }

    public ObservableCollection<ButtonBaseItem> Input { get; set; }
    public ObservableCollection<ButtonBaseItem> Report { get; set; }


    public HIDInfoSet SelectedHIDUSBDevice
    {
      get { return selectedHIDUSBDevice; }
      set 
      { 
        selectedHIDUSBDevice = value;
        if (selectedHIDUSBDevice != null)
        {
          Connect();
        }
      }
    }


    public ButtonBaseItem OutputReportItem
    {
      get { return outputReportItem; }
      set
      {
        outputReportItem = value;
        RaisePropertyChanged("OutputReportItem");
      }
    }

    /// <summary>
    /// 
    /// </summary>
    public RelayCommand UpdateButtonCommand { get; private set; }


    public RelayCommand AddButtonCommand { get; private set; }

    public RelayCommand SendReportButtonCommand { get; private set; }

    /// <summary>
    /// Initializes a new instance of the MainViewModel class.
    /// </summary>
    public MainViewModel()
    {
      HIDUSBDevices = new ObservableCollection<HIDInfoSet>();
      ButtonOutputCaps = new ObservableCollection<ButtonBaseItem>();
      ValueOutputCaps = new ObservableCollection<ValueBaseItem>();
      ButtonInputCaps = new ObservableCollection<ButtonBaseItem>();
      ValueInputCaps = new ObservableCollection<ValueBaseItem>();
      ButtonFeatureCaps = new ObservableCollection<ButtonBaseItem>();
      ValueFeatureCaps = new ObservableCollection<ValueBaseItem>();

      HidCaps = new List<string>();

      Input = new ObservableCollection<ButtonBaseItem>();
      Report = new ObservableCollection<ButtonBaseItem>();

      UpdateButtonCommand = new RelayCommand(() => ExecuteUpdate());
      AddButtonCommand = new RelayCommand(() => ExecuteAdd());

      SendReportButtonCommand = new RelayCommand(() => ExecuteSendReport());

      if (IsInDesignMode)
      {
        // Code runs in Blend --> create design time data.
      }
      else
      {
        // Code runs "for real"
      }

      ExpandHidCaps = true;

      // Start with an update of device list
      ExecuteUpdate();
    }

    private void ExecuteUpdate()
    {
      HIDUSBDevices.Clear();
      var devices = model.GetDevices();
      foreach (var device in devices)
      {
        HIDUSBDevices.Add(device);
      }
    }

    private void Connect()
    {
      try
      {
        model.Device = new HIDDevice();
        model.Device.Init(SelectedHIDUSBDevice.DevicePath);

        ButtonOutputCaps.Clear();
        foreach (var outputCap in model.Device.OutputButtonCaps)
        {
          ButtonOutputCaps.Add(outputCap);
        }

        ValueOutputCaps.Clear();
        foreach (var outputValueCap in model.Device.OutputValueCaps)
        {
          ValueOutputCaps.Add(outputValueCap);
        }

        ButtonInputCaps.Clear();
        foreach (var inputCap in model.Device.InputButtonCaps)
        {
          ButtonInputCaps.Add(inputCap);
        }

        ValueInputCaps.Clear();
        foreach (var inputValueCap in model.Device.InputValueCaps)
        {
          ValueInputCaps.Add(inputValueCap);
        }

        ButtonFeatureCaps.Clear();
        foreach (var featureCap in model.Device.FeatureButtonCaps)
        {
          ButtonFeatureCaps.Add(featureCap);
        }

        ValueFeatureCaps.Clear();
        foreach (var featureValueCap in model.Device.FeatureValueCaps)
        {
          ValueFeatureCaps.Add(featureValueCap);
        }

        HidCaps = model.Device.HidCaps;

        bw = new BackgroundWorker();

        bw.WorkerSupportsCancellation = true;
        bw.WorkerReportsProgress = true;

        bw.DoWork +=
            new DoWorkEventHandler(bw_DoWork);
        bw.ProgressChanged +=
            new ProgressChangedEventHandler(bw_ProgressChanged);
        bw.RunWorkerCompleted +=
            new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

        bw.RunWorkerAsync();
      }
      catch (Exception)
      {
        var message = new DialogMessage("Error during connect. Is a device selected? Is the device stille connected?", delegate { })
        {
          Button = MessageBoxButton.OK,
          Caption = "Error"
        };
        Messenger.Default.Send(message);
      }
    }

    #region BackgroundWorker

    private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
      // The "reader background worker thread" throws an exception when the USB
      // device is attached. Just ignore this, since the Attach/Detach
      // mechanism is handling this...
    }

    private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
      // TODO: Make smarter
      var data = e.UserState as List<ButtonBaseItem>;
      if (data != null)
      {
        foreach (var d in data)
        {
          Input.Insert(0, d);
        }
      }
    }

    private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
      // There is no input reports so just return
      if (model.Device.InputButtonCaps.Count == 0)
        return;

      var worker = sender as BackgroundWorker;
      for (;;) // Work forever
      {
        var data = model.Device.ReadInputReport();
        worker.ReportProgress(0, data);
      }
    }

    #endregion

    private void ExecuteAdd()
    {
      Report.Add(OutputReportItem);
    }

    private void ExecuteSendReport()
    {
      try
      {
        var report = new List<ButtonBaseItem>();
        foreach (var item in Report)
        {
          report.Add(item);
        }
        model.Device.WriteOutputReport(report);
      }
      catch (Exception)
      {
        var message = new DialogMessage("Error during send output report. Is the UsagePage/Usage correct?", delegate { })
        {
          Button = MessageBoxButton.OK,
          Caption = "Error"
        };
        Messenger.Default.Send(message);
      }
      Report.Clear();
    }

    ////public override void Cleanup()
    ////{
    ////    // Clean up if needed

    ////    base.Cleanup();
    ////}
  }
}